package com.whz.gateway.handler;

import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.whz.gateway.event.GatewayAlertEvent;
import com.whz.gateway.filter.GlobalCostTimeMetricFilter;
import com.whz.gateway.common.log.GatewayLog;
import com.whz.generic.api.ApiResponseWrapper;
import com.whz.generic.api.ApiStatus;
import com.whz.generic.spring.SpringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;

import java.util.Map;

import static org.springframework.web.reactive.function.server.RouterFunctions.route;


/**
 * @author whz
 */
@Slf4j
public class GlobalWebExceptionHandler extends DefaultErrorWebExceptionHandler {

    public GlobalWebExceptionHandler(ErrorAttributes errorAttributes,
                                     ResourceProperties resourceProperties,
                                     ErrorProperties errorProperties,
                                     ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties, applicationContext);
    }

    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return route(RequestPredicates.all(), request -> {
            Map<String, Object> error = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));

            //log.error("Gateway Global Handler Catch 'error': \n {}.", error);

            Throwable throwable = getError(request);

            //log.error("Gateway Global Handler Catch 'throwable'.", throwable);

            int status = getHttpStatus(error);
            String errorMsg = getErrorMsg(throwable);

            if (BlockException.isBlockException(throwable)) {
                status = HttpStatus.TOO_MANY_REQUESTS.value();
                errorMsg = HttpStatus.TOO_MANY_REQUESTS.getReasonPhrase();
            }

            sendAlert(request, throwable);

            ApiResponseWrapper<Map<String, Object>> response = new ApiResponseWrapper<>();
            response.setStatus(ApiStatus.FAILURE);
            response.setPayLoad(error);
            response.setError(errorMsg);

            return ServerResponse.status(status)
                                 .contentType(MediaType.APPLICATION_JSON)
                                 .body(BodyInserters.fromValue(response.fromGateway()));
        });
    }

    private static void sendAlert(ServerRequest request, Throwable throwable) {
        try {
            GatewayLog curLog = GlobalCostTimeMetricFilter.currentGatewayLog(request.exchange());
            curLog.setError(throwable.getMessage());
            log.error("Gateway Global Handler Catch Error: \n {}", curLog, throwable);
            SpringUtils.publishEvent(new GatewayAlertEvent(curLog));
            return;
        } catch (Exception ignored) {
        }
        log.error("Gateway Global Handler Catch Error.", throwable);
    }

    private static String getErrorMsg(Throwable throwable) {
        return throwable != null? throwable.getMessage() : "Gateway Internal Error";
    }
}